package svc.elib.socnet;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.collections15.Transformer;


import svc.elib.db.Author;
import svc.elib.db.Paper;

import edu.uci.ics.jung.algorithms.scoring.DistanceCentralityScorer;
import edu.uci.ics.jung.algorithms.shortestpath.Distance;
import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics;
import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
import edu.uci.ics.jung.graph.UndirectedSparseGraph;
import edu.uci.ics.jung.graph.util.EdgeType;

/**
 * Digital library network
 * 
 * @author svc
 * @date 7.6.2013
 */
public class Net implements Comparable<Net> {

	private String name;
	
	private UndirectedSparseGraph<Author, Link> g =
		new UndirectedSparseGraph<Author, Link>();
	
	private HashMap<String, Author> authorMap = 
		new HashMap<String, Author>();

	private int numIsolated = Integer.MIN_VALUE;
	private double averageDegree = Double.MIN_VALUE;

	private Author maxDegreeAuthor;
	private int maxDegree;
	
	private UnweightedShortestPath<Author, Link> usp = null;
	
	public Net(String name) {
		this.name = name;
	}
		
	public void createNode(Author a) {
		g.addVertex(a);
		authorMap.put(a.getName(), a);
	}
	
	/**
	 * Creates new collaboration link. At the same time it will determine
	 * the list of papers in common for src and dst author
	 * 
	 * @param src
	 * @param dst
	 */
	public void createLink(Author src, Author dst) {
		Link l = g.findEdge(src, dst);
		if (l == null) {
			l = new Link(src, dst);
			g.addEdge(l, src, dst, EdgeType.UNDIRECTED);
		}
	}
	
	public void createLink(Author src, Author dst, LinkedList<Paper> papersInCommon) {
		Link l = g.findEdge(src, dst);
		if (l == null) {
			l = new Link(src, dst, papersInCommon);
			g.addEdge(l, src, dst, EdgeType.UNDIRECTED);
		}
	}
	
	public void degreeStatistics() {
		Collection<Author> authors = g.getVertices();
		Iterator<Author> it = authors.iterator();
		
		numIsolated = 0;
		maxDegree = Integer.MIN_VALUE;
		int sum = 0;
		
		while (it.hasNext()) {
			Author a = it.next();
			int aDeg = g.degree(a);
			
			if (aDeg == 0)
				++numIsolated;
			
			if (aDeg > maxDegree) {
				maxDegree = aDeg;
				maxDegreeAuthor = a;
			}
			
			sum += aDeg;
		}
		
		if (numIsolated == authors.size())
			averageDegree = 0;
		else
			averageDegree = (double) sum / (double) (authors.size() - numIsolated);
	}
	
	public void removeIsolated() {
		Collection<Author> authors = g.getVertices();
		Iterator<Author> it = authors.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			int aDeg = g.degree(a);	
			if (aDeg == 0) {
				g.removeVertex(a);
			}
		}
	}

	public void printInfo() {
		degreeStatistics();
		
		System.out.println("Network " + name);
		System.out.println("# nodes = " + g.getVertexCount());
		System.out.println("# links = " + g.getEdgeCount());
		System.out.println("# isolated = " + numIsolated);
		System.out.println("# max degree = " + maxDegree);
		System.out.println("# max degree author: " + maxDegreeAuthor.getInfo());
		System.out.println("Average degree (isolated excluded) = " + averageDegree);
	}
	

	@Override
	public int compareTo(Net o) {
		return o.g.getVertexCount() - this.g.getVertexCount();
	}

	public UndirectedSparseGraph<Author, Link> getGraph() {
		return g;
	}
	
	public String getName() {
		return name;
	}

	public void dumpAuthors() {
		LinkedList<String> aList = new LinkedList<String>();
		Collection<Author> authors = g.getVertices();
		Iterator<Author> it = authors.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			aList.addLast(a.getName());
		}
		
		Collections.sort(aList);
		for (int i = 0; i < aList.size(); i++) {
			System.out.println(aList.get(i));
		}
	}

	public int getNumNodes() {
		return g.getVertexCount();
	}

	public double getDensity() {
		double numLinks = g.getEdgeCount();
		double n = g.getVertexCount();
		double denominator = n * (n - 1) / 2.0;
		
		if (denominator == 0)
			return 0;
		
		return numLinks / denominator;
	}

	public int getNumLinks() {
		return g.getEdgeCount();
	}

	public int getNumIsolated() {
		if (numIsolated == Integer.MIN_VALUE)
			degreeStatistics();
		
		return numIsolated;
	}
	
	public double numIsolatedFrac() {
		if (numIsolated == Integer.MIN_VALUE)
			degreeStatistics();
		
		double f = (double) numIsolated / (double) g.getVertexCount();
		int f100 = (int) (f * 100.0);
		return (double) f100 / 100.0;
	}
	
	public double getAverageDegree() {
		if (averageDegree == Double.MIN_VALUE)
			degreeStatistics();
		
		return averageDegree;
	}
	
	public double getCC(Author a) {
		if (g.degree(a) > 1) {
			LinkedList<Author> nlist = new LinkedList<Author>();
			Collection<Author> nei = g.getNeighbors(a);
			Iterator<Author> neiIt = nei.iterator();
			while (neiIt.hasNext()) {
				nlist.add(neiIt.next());
			}
			
			int sum = 0;
			for (int i = 1; i < nlist.size(); i++) {
				for (int j = 0; j < i; j++) {
					if (g.findEdge(nlist.get(i), nlist.get(j)) != null) {
						sum++;
					}
				}
			}
			
			double local = (double) 2.0 * sum / (double) (nlist.size() * (nlist.size() - 1));
			return local;
		} 
		else 
			return 0;
	}
	
	public double getCC() {
		
		double CCSum = 0.0;
		double CCNum = 0.0;
		
		Collection<Author> ac = g.getVertices();
		Iterator<Author> it = ac.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (g.degree(a) > 1) {
				double local = getCC(a);
				CCSum += local;
				CCNum += 1.0;
			} 
			else 
			if (g.degree(a) == 1) {
				CCNum += 1.0;
			}
		}
		
		if (CCNum == 0.0)
			return 0.0;
		
		double res = CCSum / CCNum;
		int res100 = (int) (res * 100.0);
		return (double) res100 / 100.0;
	}
	
	public double getSW() {
		Transformer<Author, Double> distances = 
				DistanceStatistics.averageDistances(g, new UnweightedShortestPath(g));
			
		double sum = 0.0;
		double num = 0.0;
		
		Collection<Author> coll = g.getVertices();
		Iterator<Author> it = coll.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (g.degree(a) > 0) {
				double d = distances.transform(a);
				sum += d;
				num += 1.0;
			}
		}
		
		if (num == 0)
			return 0.0;
			
		return sum / num;
	}
	
	public double getSW2() {
		DistanceCentralityScorer<Author, Link> ranker =
			new DistanceCentralityScorer<Author, Link>(g, true, true, true);
		
		double sum = 0.0;
		double num = 0.0;
		
		Collection<Author> nodeCol = g.getVertices();
		Iterator<Author> nodeIt = nodeCol.iterator();
		while (nodeIt.hasNext()) {
			Author a = nodeIt.next();
			if (g.degree(a) > 0) {
				double d = ranker.getVertexScore(a);
				sum += 1/d;
				num += 1.0;
			}
		}
		
		if (num == 0)
			return 0.0;
			
		return sum / num;
		
	}
	
	public double getSW3() {
		
		double sum = 0.0;
		double num = 0.0;
		
		UnweightedShortestPath<Author, Link> usp = new UnweightedShortestPath<Author, Link>(g);
		
		Collection<Author> nodeCol = g.getVertices();
		Iterator<Author> nodeIt = nodeCol.iterator();
		while (nodeIt.hasNext()) {
			Author a = nodeIt.next();
			if (g.degree(a) > 0) {
				Map<Author, Number> distanceMap = usp.getDistanceMap(a);
				Iterator<Entry<Author, Number>> it = distanceMap.entrySet().iterator();
				double _sum = 0.0;
				double _num = 0.0;
				while (it.hasNext()) {
					Entry<Author, Number> e = it.next();
					//Author dst = e.getKey();
					double localSW = (double) e.getValue().intValue();
					if (localSW > 0) {
						_sum += localSW;
						_num += 1.0;
					}
				}
				
				if (_num == 0)
					continue;
				
				double avgSW = _sum / _num;
				
				
				
				sum += avgSW;
				num += 1.0;
			}
		}
		
		if (num == 0)
			return 0.0;
			
		return sum / num;
	}
	
	public double getDiameter() {
		Distance<Author> d = new UnweightedShortestPath<Author, Link>(g);
		return DistanceStatistics.diameter(g, d, true);
	}

	public int getNumMaleAuthors() {
		int cnt = 0;
		
		Collection<Author> authors = g.getVertices();
		Iterator<Author> it = authors.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (a.isMale())
				++cnt;
		}
		
		return cnt;
	}
	
	public int getNumFemaleAuthors() {
		int cnt = 0;
		
		Collection<Author> authors = g.getVertices();
		Iterator<Author> it = authors.iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (!a.isMale())
				++cnt;
		}
		
		return cnt;
	}
	
	public double maleFemaleRatio() {
		double t = (double) getNumMaleAuthors() / (double) getNumFemaleAuthors();
		int t100 = (int) (t * 100.0);
		return (double) t100 / 100.0;
	}

	public int getNumAuthors() {
		return g.getVertexCount();
	}

	public boolean connected(Author a, Author b) {
		return g.findEdge(a, b) != null || g.findEdge(b, a) != null;
	}

	public Link getLink(Author a1, Author a2) {
		return g.findEdge(a1, a2);
	}

	public int shortestPath(Author a, Author b) {
		if (usp == null)
			usp = new UnweightedShortestPath<Author, Link>(g);
		
		Number dist = usp.getDistance(a, b);
		if (dist == null)
			return Integer.MAX_VALUE;
		
		return dist.intValue();
	}

	public Author findAuthor(String authorName) {
		return authorMap.get(authorName);
	}
	
	public static int __maxDeg;
	
	public LinkedList<Author> getHighestDegreeAuthor() {
		int highestDegree = Integer.MIN_VALUE;
		//Author retA = null;
		
		Iterator<Author> it = g.getVertices().iterator();
		while (it.hasNext()) {
			Author a = it.next();
			int deg = g.degree(a);
			if (g.degree(a) > highestDegree) {
				highestDegree = deg;
				//retA = a;
			}
		}
		
		__maxDeg = highestDegree;
		
		LinkedList<Author> list = new LinkedList<Author>();
		
		it = g.getVertices().iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (g.degree(a) == highestDegree) {
				list.add(a);
			}
		}
		
		return list;
	}
	
	public static int __numPapers = 0;
	
	public LinkedList<Author> getAuthorWithMaxPapers(int endYear) {
		int maxPapers = Integer.MIN_VALUE;
		//Author retA = null;
		
		Iterator<Author> it = g.getVertices().iterator();
		while (it.hasNext()) {
			Author a = it.next();
			int deg = a.getNumPapers(endYear);
			if (deg > maxPapers) {
				maxPapers = deg;
				//retA = a;
			}
		}
		
		__numPapers = maxPapers;
		
		LinkedList<Author> list = new LinkedList<Author>();
		it = g.getVertices().iterator();
		while (it.hasNext()) {
			Author a = it.next();
			if (maxPapers == a.getNumPapers(endYear)) {
				list.add(a);
			}
		}
		
		return list;
	}
	
	public static int __maxBet;
	
	public Author getAuthorWithMaxBet() {
		CentralityMetrics cm = new CentralityMetrics(this);
		cm.computeBetweeness(false);
		
		int maxBet = Integer.MIN_VALUE;
		Author retA = null;
		
		Iterator<Author> it = g.getVertices().iterator();
		while (it.hasNext()) {
			Author a = it.next();
			int deg = (int) cm.getBetweenness(a.getName());
			if (deg > maxBet) {
				maxBet = deg;
				retA = a;
			}
		}
		
		__maxBet = maxBet;
		
		return retA;
	}
}
 